Function Assert-AdminRight(){#check if the current powershell process is running as administrator
	return ([Security.Principal.WindowsPrincipal]([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
Function Pop-Message(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$info,
        [Parameter(Mandatory=$False)]
        [string]$window_title='Script Running Error',
        [string]$info_type='Error'
    )
    [void][System.Windows.Forms.MessageBox]::Show($info, $window_title, 'OK', $info_type)
}
Function Write-Log(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$true)]
        [ValidateSet("Warning", "Error", "Success","Normal","Critical")]
        [string]$log_type,
        [string]$log_info,
        [string]$log_file="c:\temp\log.txt",
        [Parameter(Mandatory=$false)]
        [switch]$print_to_console
    )
    If( $print_to_console ){
        $color = switch -regex ($log_type){
            "Warning"        {'yellow';break}
            "Success"        {'green' ;break}
            "Error|Critical" {'red'   ;break}
            default          {'white'       }
        }
        write-host -fore $color $log_info
    } $loop_cnt=0
    Do{
        Try{"{0:yyyy-MM-dd HH:mm:ss.fff}--{1}: {2}" -f (get-date),$log_type,$log_info |Out-File -FilePath $log_file -Append -Encoding utf8 -Force -ea 1;break}Catch{sleep -Milliseconds 200 ;if(++$loop_cnt -gt 10){write-error "wait too long(2s) to write log: $_";break}
        }Finally{}
    }While($loop_cnt -lt 10)
}
Function Connect-FTP(){
    [CmdletBinding()]param(
		[Parameter(Mandatory=$false)]
		[ValidateNotNullOrEmpty()]
		[string]$Username,
		[Parameter(Mandatory=$false)]
		[string]$Password,
		[Parameter(Mandatory=$false)]
		[string]$ftpServer='isgswapd1n1',
		[Parameter(Mandatory=$true)]
		[string]$remote_file,
		[Parameter(Mandatory=$true)]
		[string]$local_file,
		[Parameter(Mandatory=$false)]
		[ValidateSet("Download", "Upload")]
		[string]$ftp_action="Download",
		[Parameter(Mandatory=$false)]
		[string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
	)
    $remote_path ="$ftpServer/$remote_file"
    $ftp_server_uri="ftp://"+$Username+":"+$Password+"@"+$ftpServer
	$webclient = New-Object System.Net.WebClient
	$remote_file_uri = New-Object System.Uri("$ftp_server_uri/$remote_file")
    Switch ($ftp_action){
        "Download"{
            Try{
                $webclient.DownloadFile($remote_file_uri, $local_file)
                Write-Log Success "Have downloaded file $local_file from $remote_path" $log_file ; return $true
            }Catch{
                Write-Log Error $_.exception $log_file ; return $false
            }Finally{}
            }
        "Upload"{
            Try{
                If( test-path $local_file){
                    $webclient.UploadFile($remote_file_uri, $local_file); 
                    Write-Log Success "Have uploaded file $local_file to $remote_path" $log_file; return $true
                }else{
                    Write-Log Error "File localFile doesn't exist at all, please double check" $log_file ; return $false
                }
            }Catch{
                Write-Log Error $_.exception $log_file ; return $false
            }Finally{}
        }
    }
}
Function Connect-RemoteComputer(){
    [CmdletBinding()]
	param(
		[Parameter(Mandatory=$true)]
		[Alias("cn")]$ComputerName,
		[Parameter(Mandatory=$false)]
        [ValidatePattern("^(nam|apac|eur)\\.{7,9}")]
		$user = "nam\aaadmin",
		[Parameter(Mandatory=$false)]
		$passwd=$aaadminPWD,
		[Switch]$resize
	)
	if($user -inotmatch "^aaadmin(_p)?"){
		try{
            while(($passwd -eq $null) -or ($passwd.trim() -eq "")){
                $passwd = Get-Credential -UserName $user -Message "Please enter password again as it was empty!"
                $passwd = $passwd.GetNetworkCredential().Password
            }
		}catch{}finally{}
	}else{
		if($user -eq "nam\aaadmin_p"){$passwd =$aaadmin_pPWD}
	}
    cmdkey.exe /generic:$ComputerName /user:$user /pass:$passwd
	If( !$resize ){mstsc.exe /v $ComputerName /w 1804 /h 1015}else{mstsc.exe /v $ComputerName /w 1376 /h 768}
}
Function Get-CmdkeyList(){
    $ckList = cmdkey /list|?{$_ -imatch 'Target|User'}; $ckPair=@() # might use hashtable?
	for($n=0;$n -le $ckList.count;$n=$n+2){
		try{
			$ckPair+=$ckList[$n].trim().split("=")[-1] + "-"+$ckList[$n + 1].split(":")[-1].trim()
		}catch{}
	}
	return $ckPair
}
Function Remove-Cmdkey(){
    [CmdletBinding()]param(
		[Parameter(Mandatory=$True,ParameterSetName="config",Position=0)]
		[string]$domain,
		[Parameter(Mandatory=$True,ParameterSetName="config",Position=1)]
		[string]$env,
		[Parameter(Mandatory=$False,ParameterSetName="config",Position=2)]
		[string]$node="1",
		[Parameter(Mandatory=$True,ParameterSetName="vdi")]
		[string]$vdi
	)
	If(!$PSBoundParameters.ContainsKey('vdi') ){
		$ComputerName=Get-XMLValue $powershell_config "//$domain/$env/n$node" 
		cmdkey /delete:$ComputerName
	}else{cmdkey /delete:$vdi}
}
Function Set-FIDPwd(){ # have not tested this function yet,to be updated
	[cmdletBinding()]param(
        [ValidatePattern("^(nam|apac|eur)\\\w{7,9}")]
        [string]$fid,
		[Parameter(Mandatory=$False)]
		[string]$domain="NAM",
		[Parameter(Mandatory=$True)]
		[string]$user,
		[Parameter(Mandatory=$True)]
		[string]$tempPwd,
		[Parameter(Mandatory=$False)]
		[string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
	)
	If( [ADSI]::Exists("WinNT://{0}/{1},user" -f $adobject.split('\')) ){
		try{
			$objuser = Get-ADUser $domain $user
			$objuser.psbase.invoke("SetPassword",$tempPwd )
			#0 to enable the User must change password at next logon option 
			#-1 to disable the User must change password at next logon option
			# $objuser.psbase.invokeSet("pwdLastSet",0)
			$objuser.setinfo()
			return $true
		}catch{
			return $false
		}finally{}
	}
}
Function Enable-RemotePS(){ # to be updated
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$computer,
        [Parameter(Mandatory=$False)]
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
	if(Test-Connection $computer -quiet -count 1 -ea 0){
		try{#add excetion filewall rule so that "Enable-PSRemoting –force" can run successfully
			& psexec.exe \\$computer netsh advfirewall firewall show rule name="Windows Remote Management (HTTP-In)" dir=in 2>$null
			if($lastExitCode -eq 1){
				& psexec.exe \\$computer netsh advfirewall firewall add rule name="Windows Remote Management (HTTP-In)" dir=in action=allow service=any enable=yes profile=any localport=5985 protocol=tcp 2>$null
				if($lastExitCode -ne 0){
					Write-Log -logType Error "Failed to add filrewall rule in $computer" $currentLog
					return $false
				}
			}elseif($lastExitCode -eq 0){#enable powershell remote PC connecting
				& psexec.exe \\$computer powershell.exe "Enable-PSRemoting -force" 2>$null
				if($lastExitCode -ne 0){
					Write-Log -logType Error "Failed to Enable-PSRemoting in $computer" $currentLog
					return $false
				}
				return $true
			}
		}catch{
			Write-Log -logType Error "Cannot connect $computer from remote powershell" $currentLog
			return $false
		}finally{}
	}else{
		Write-Log -logType Error "$computer is not online" $currentLog
		return $false
	}
}
Function Get-SoftwareReg(){ # the filter pattern needs to be optimized in the future for less time checking
	[CmdletBinding()]param(
		[Parameter(Mandatory=$True,ParameterSetName="one",Position="0")]
		[string]$software,
		[Parameter(Mandatory=$False,ParameterSetName="one")]
		[string]$version="*",
		[Parameter(Mandatory=$False,ParameterSetName="all")]
		[Switch]$all
	)
    $reg64_path='HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
    $reg32_path='HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
	If($all){
        $reg64_path,$reg32_path|%{$software_reg+=(dir $_ |? {$_.name -notmatch '(\.)?KB\d+'}|Get-ItemProperty)}
		return $software_reg
	}else{
		try{
			$software_reg = dir $reg32_path |Get-ItemProperty|?{$_.DisplayVersion -like "*$version*" -and $_.displayname -like "*$software*"}
			if($software_reg -eq $null){
				$software_reg = dir $reg64_path|Get-ItemProperty|?{$_.DisplayVersion -like "*$version*" -and $_.displayname -like "*$software*"}
			}
			return $software_reg
		}catch{
			return $null
		}finally{}
	}
}
Function Enter-LeftMouse(){
    $signature=@' 
        [DllImport("user32.dll",CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)]
        public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
'@ 
	$SendMouseClick = Add-Type -memberDefinition $signature -name "Win32MouseEventNew" -namespace Win32Functions -passThru 
	$SendMouseClick::mouse_event(0x00000002, 0, 0, 0, 0); #0x00000002 corresponds to a left-mouse-down move
	$SendMouseClick::mouse_event(0x00000004, 0, 0, 0, 0); #0x00000004 corresponds to a left-mouse-up move   
}

Function Send-Keys($string){  # sendkey & screenshot not working when the user screen is locked
	[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
	$sw = "[System.Windows.Forms.SendKeys]::SendWait("
	switch -regex ($string){
        "tab"   {$string = $sw + "`"{TAB}`")";break}
        "enter" {$string = $sw + "`"{ENTER}`")";break}
        "down"  {$string = $sw + "`"{DOWN}`")";break}
        "right" {$string = $sw + "`"{RIGHT}`")";break}
        default {$string = $sw + "`"$string`")"}
	}
	iex $string ;start-sleep -Milliseconds 250;
}
Function Get-RAMSize([array]$computers=$env:COMPUTERNAME,[string]$by_size="GB"){
    return (Get-WmiObject CIM_PhysicalMemory -ComputerName $computers|select @{n="VDI";e={$_.pscomputername}},@{n="RAM-$by_size";e={"{0:0.00}"-f (($_.Capacity|Measure-Object -Sum).sum / "1$by_size")}})
}
Function Get-HardDiskSize([array]$computers=$env:COMPUTERNAME,[string]$disk='C',[string]$by_size="GB"){
    return (Get-WmiObject Win32_LogicalDisk -cn $computers|?{$_.DeviceID -imatch "^$disk"}|select VolumeName, @{n="$disk-$by_size";e={"{0:0.00}" -f ($_.Size / "1$by_size") }})
}
Function New-HashTableFromFile($config_file){ # to be updated to hashtable
	$obj=@{}
	$pro_list=(get-content $config_file)|?{![string]::IsNullOrWhiteSpace($_.Trim()) -and !$_.startswith('#')}
	try{
		foreach($line in $pro_list){
			$key,$value=$line.split("=")
			$obj.Add($key,$value)
		}
		return $obj
	}catch{
		return $null
	}
}
Function Save-Screenshot_old_function(){
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$True)]
        [string]$save_file,
        [Parameter(Mandatory=$False)]
        [ValidateSet("BMP","JPEG","GIF","TIFF","PNG")]
        [string]$image_encoder='PNG',
        [switch]$all_screen,
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
    begin {
        Add-Type -AssemblyName System.Drawing
        Add-Type -AssemblyName System.Windows.Forms
    }
    process {
        $loop_cnt=0
        Do{
            If( $all_screen ){ $send_value="{PrtSc}" #capture all the screen
            }else{$send_value="%{PrtSc}" } #capture active window screen
            [Windows.Forms.Sendkeys]::SendWait($send_value)
            sleep 1
            $bitmap = [Windows.Forms.Clipboard]::GetImage()
            If( $loop_cnt++ -gt 5 ){
                return $false
            }
        }While( $bitmap -eq $null )
        If( $bitmap -ne $null ){
            $image_codec = [Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() | Where-Object { $_.FormatDescription -eq $image_encoder}
            $ep = New-Object Drawing.Imaging.EncoderParameters
            $ep.Param[0] = New-Object Drawing.Imaging.EncoderParameter ([System.Drawing.Imaging.Encoder]::Quality, [long]100)
            if(test-path $save_file -ea 0){remove-item $save_file -Force -ea 0}
            Try{
                $bitmap.Save($save_file, $image_codec, $ep) ; return $true
            }Catch{
                Write-Log error $_.exception $log_file ; return $false
            }Finally{}
        }
    }
}
Function Save-Screenshot($save_file){
    # sendkey & screenshot not working when the user screen is locked
    [void][Reflection.Assembly]::LoadWithPartialName("System.Drawing")
    $bounds = [Drawing.Rectangle]::FromLTRB(0, 0, 1920, 1080)
    $bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
    $graphics = [Drawing.Graphics]::FromImage($bmp)
    $graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
    $bmp.Save($save_file) 1>$null
    $graphics.Dispose() 1>$null
    $bmp.Dispose() 1>$null  
}
Function Save-OpenedRDCScreen($save_directory="$home\Pictures" ){
    [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic")
	del "$home\Pictures\*.png" -Force -ErrorAction Ignore
	Foreach( $rdc in (Get-OpenedRDC) ){
        [Microsoft.VisualBasic.Interaction]::AppActivate($rdc); sleep 2
		Save-Screenshot "$save_directory\$rdc.png"
	}
}

Function Get-IEComObject(){ # to be updated
    [CmdletBinding()]param(
        [Parameter(Mandatory=$False)]
        [switch]$Show,
        [Switch]$log
    )
	$ie=$null
	Foreach( $i in (New-Object -ComObject Shell.Application).Windows()){
	    If($i.Name  -like "*Internet Explorer*"){
	       $ie=$i ; break #$ie.FullScreen=$true
	    }
	}
	If( $ie -eq $null ){
		$loop_cnt=0
		do{
			try{
				$ie=new-object -com InternetExplorer.Application
			}catch{ sleep 1
				if(++$loop_cnt -gt 10){
					if($log){Write-Log -logType Error "Waiting too long(10s) for IE open" $log} return $null
				}
			}finally{}
		}while($loop_cnt -lt 10)
	}
    # $asm = [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
    # $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds|select Width,Height
    # $ie.width,$ie.height=$screen.Width,$screen.Height
    # $ie.Top =  0 ;   $ie.Left = 0 ;$ie.MenuBar=$false
	$ie.visible = $Show 
	return $ie
}
Function Clear-IEComObject($ie){
    $ie.Quit()
    [void][System.Runtime.Interopservices.Marshal]::ReleaseComObject($ie)
    Remove-Variable ie 
}

Function Get-OutlookInbox(){
	Try{
		Add-Type -assembly "Microsoft.Office.Interop.Outlook"
		$Outlook = New-Object -comobject Outlook.Application
		$namespace = $Outlook.GetNameSpace("MAPI")
		$inbox = $namespace.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox)
		return $inbox
	}Catch{
	    return $null
	}Finally{}
}
Function Get-OutLookMails(){
	[CmdletBinding()]
    param(
		[Parameter(Mandatory=$True)]
		[__ComObject]$mail_folder,
		[Parameter(Mandatory=$False)]
		[ValidateScript({($_|Get-Date) -lt (get-date)})]
		[string]$from=(get-date).ToString("MM/dd/yyyy"),
		[Switch]$Recurse
	)
	$mail_folderPath = ($mail_folder.FullFolderPath -split "com\\")[1]
	write-host "Checking mail in folder $mail_folderPath"
	$mails=@()
    Foreach($item in $mail_folder.Items){
		If(($itime=$item.ReceivedTime) -ne $null){
			if($itime -gt ([DateTime]$from)){
				Add-Member -InputObject $item -Name InFolder -Value $mail_folderPath -MemberType NoteProperty
				$mails+=$item
			}else{break}
		}
	}
	If( $Recurse ){
		Foreach( $folder in $mail_folder.Folders){
			If($folder.Folders.Count -ne 0){
				Get-OutLookMails $folder -Recurse
			}else{
				Get-OutLookMails $folder
			}
		}
	}
	return $mails
	# $inbox.Items|select -First 100|Group-Object -Property Subject -NoElement | Sort-Object count
}
Function Send-MailToTeam(){ # this is a template funtion
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$error_info
    )
$body = @"
<p>Hi RPA AA STE Support,</p>
<p>An error was found while monitoring file modified date with below details:</p>
<table border="1">
<tr><td>Monitor File</td><td>$to_be_checked_file</td></tr>
<tr><td>Expected $match_property</td><td>$match_property_value</td></tr>
<tr><td bgcolor="#FF0000">Error</td><td bgcolor="#FF0000">$error_info</td></tr>
</table>
<p>Thank You!</p>
aaadmin
"@
    $this_user=$env:username
    $this_computer=$env:computername
    $this_env=Switch -regex ( $this_computer.Substring(7,3)){                    # this computer environment
        "p2n"               {"PROD";break}
        "u2n"               {"UAT" ;break}
        "-f2|-18|-aa|-50"   {"DEV" ;break}
        "-44|-97|-c2"       {"POC" ;break}
        "-f3|-3d|-c5"       {"LAB" ;break}
        "c2n"               {"COB" ;break}
        "e2n"               {"PTE" ;break}
        default             {$this_computer}
    }
    $this_node=Switch -regex ($this_computer.substring($this_computer.length -7)){
        "((^ap.*01)|(49|E2|9a))$"  {"1";break}
	    "(02|b8|0e|ae)$"           {"2";break}
	    "(03|72|27|50)$"           {"3";break}
	    "(26|04|27|fe)$|(db.*01$)" {"4"      }
    }
    $this_env=("[$this_env-{0}-$this_node]" -f (Get-WmiObject Win32_ComputerSystem).Domain.Split(".")[0]).toupper()
    Send-MailMessage `
        -From "$this_user.$this_computer@imcnam.ssmb.com" `
        -To dl.gt.cn.cstc.rpa.aa.ste@imcnam.ssmb.com `
        -Subject "$this_env RPA AA STE File Last Modify Time Monitor - ERROR" `
        -Body $body -BodyAsHtml -Priority High `
        -SmtpServer "imta.citicorp.com" -Port 25 `
        -Encoding([System.Text.Encoding]::UTF8)
}
Function Send-OutlookEmail(){ # to be updated
	[cmdletbinding()]
	Param (
		[Parameter(Mandatory=$False,Position=0)]
		[String]$To="cw50406@imcnam.ssmb.com",
		[Parameter(Mandatory=$False,Position=1)]
		[String]$Subject="test email from powershell",
		[Parameter(Mandatory=$False,Position=2)]
		[String]$Body="test email from powershell"
	)
	Begin {
		Clear-Host ; Add-Type -assembly "Microsoft.Office.Interop.Outlook"
	}
	Process {
		# Create an instance Microsoft Outlook
		$Outlook = New-Object -ComObject Outlook.Application
		$Mail = $Outlook.CreateItem(0)
		$Mail.To = $To
		$Mail.Subject = $Subject
		$Mail.Body =$Body
		# $Mail.HTMLBody = "When is swimming?"
		# $File = "D:\CP\timetable.pdf"
		# $Mail.Attachments.Add($File)
		$Mail.Send()
        write-host "mail sent"
	} # End of Process section
	End { # Section to prevent error message in Outlook
		#$Outlook.Quit()  this will quit the outlook process
		[void][System.Runtime.Interopservices.Marshal]::ReleaseComObject($Outlook)
		Remove-Variable Outlook
	} # End of End section!
}
Function Get-CRTaskResultFromDB(){ # to be updated
    [CmdletBinding()]
    param(
    	[Parameter(Mandatory=$True)]
    	[string]$DBServer,
    	[string]$DBPwd,
    	[string]$user,
    	[string]$task,
    	[string]$taskPath,
    	[string]$cmp,
		[Parameter(Mandatory=$False)]
    	[string]$log_file="$home\Documents\WindowsPowerShell\db_log.txt"
    )
	If(($connection = Connect-DB $DBServer $DBPwd -logfile $log_file) -ne $null){
		$uquery = "SELECT id  FROM [ICGDB].[dbo].[Users] where UserName = '$user' and IsDeleted=0"
		If( ($utable  = Execute-Sql $connection $uquery $log_file) -ne $null ){# check if FID exists in CR
			$userId = $utable.ID
			$tquery = "SELECT id FROM [ICGDB].[dbo].[Tasks] where FileName='$task' and RelativePath like '%$taskPath'"
			$cquery = "SELECT id FROM [ICGDB].[dbo].[Clients] where HostName='$cmp' and UserId='$userId'"
			If((($ttable  = Execute-Sql $connection $tquery $log_file) -ne $null) -and (($ctable = Execute-Sql $connection $cquery $log_file) -ne $null)){# check if task exists in CR
				$taskId=$ttable.ID ; $ClientId = $ctable.ID
				If( $taskId.GetType().Name -eq "Object[]" ){
				    write-error "Have found bulk task ID for $task,please re-edit and run again" $log_file ; return $false}
				$trdquery="
					SELECT StartTime FROM [ICGDB].[dbo].[TaskRunDetails] where 
					ClientId='$ClientId' and TaskId='$taskId' and Status='Completed' and IsTaskExecutionCompleted=1
				"
				$aqurey="
					SELECT Status FROM [ICGDB].[dbo].[Activities] where UserName='$user' and 
					ActivityType='Run Task' and ActivityAt > CONVERT(VARCHAR(30),DATEADD(MINUTE,210,getdate()),120) --the default duration is within 30 mins
					and charindex('$task',EventDescription)>0 and charindex('Run Duration',EventDescription)>0
				"
				If((($trdtable  = Execute-Sql $connection $trdquery $log_file) -ne $null) -and (($atable= Execute-Sql $connection $aqurey $log_file) -ne $null)){
					If( $atable.Status -ieq "Success" ){
						$TaskRunDetails=@()
						If( $trdtable.GetType().Name -eq "DataRow" ){$TaskRunDetails+=($trdtable.StartTime) # only returns one line of data
						}elseif( $trdtable.GetType().Name -eq "Object[]" ){$TaskRunDetails+=$trdtable|%{$_.StartTime}}# returns bulk lines of data
						Write-Log Success "Have found the completed task $task ran in $cmp" $log_file
						$connection.close() ; return $TaskRunDetails
					}else{ Write-Log Error "Task $task failed running in VDI $cmp" $log_file	}
				}else{Write-Log Error "Task $task was not found running in VDI $cmp" $log_file}
			}else{Write-Log Error "Task $task or VDI $cmp was not found in $domain\$env control room" $log_file}		
		}else{Write-Log Error "FID $domain\$user was not found in $domain\$env control room" $log_file}
		Close-DB $connection; return $false
	}
}
Function Get-CRUserFromDB(){ # to be updated
    [CmdletBinding()]
    param(
    	[Parameter(Mandatory=$True)]
    	[string]$DBServer,
    	[string]$DBPwd,
    	[string]$user,
    	[Parameter(Mandatory=$false)]
    	[Array]$role,
    	[Array]$licence,
        $log_file="$home\Documents\WindowsPowerShell\db_log.txt"
    )
	If(($connection = Connect-DB $DBServer $DBPwd -logfile $log_file) -ne $null){
		$uquery = "SELECT id  FROM [ICGDB].[dbo].[Users] where UserName = '$user' and IsDeleted=0"
		If( ($utable  = Execute-Sql $connection $uquery $log_file) -ne $null ){# check if FID exists in CR
			$userId = [String]$utable.ID
			$rquery = "
				DECLARE @t TABLE(RoleName varchar(50)) -- difine table to store results
				DECLARE @RoleId varchar(50) -- define curser
				DECLARE My_Cursor CURSOR
				FOR SELECT RoleId FROM [ICGDB].[dbo].[UserRoles] where userid ='$userId' -- put the clollections in the curser
				OPEN My_Cursor;
				FETCH NEXT FROM My_Cursor INTO @RoleId; -- fetch the 1st line in RoleId in the curser
				WHILE @@FETCH_STATUS = 0
					BEGIN
						insert into @t values((SELECT name FROM [ICGDB].[dbo].[Roles] where id=@RoleId))
						FETCH NEXT FROM My_Cursor INTO @RoleId; -- fetch the next line in RoleId in the curser
					END
				CLOSE My_Cursor; --close cursor
				DEALLOCATE My_Cursor; --release curser
				select * from @t
			"
			# If(($rtable  = Execute-Sql $connection $rquery $log_file) -ne $null ){# check if FID has roles in CR
				$rtable  = Execute-Sql $connection $rquery $log_file # if FID exist in CR, it must has roles
				$foundRole=@()
				If( $rtable.GetType().Name -eq "DataRow" ){$foundRole+=$rtable.RoleName # only returns one line of data
				}elseif( $rtable.GetType().Name -eq "Object[]" ){$rtable|%{$foundRole+=$_.RoleName}}# returns bulk lines of data
				$lquery = "SELECT Feature FROM [ICGDB].[dbo].[Features] where userid='$userId'"
				If( ($ltable  = Execute-Sql $connection $lquery $log_file) -ne $null ){# check if FID has licences
					$foundLicence=@()
					If( $ltable.GetType().Name -eq "DataRow" ){$foundLicence+=$ltable.Feature # only returns one line of data
					}elseif( $ltable.GetType().Name -eq "Object[]" ){$ltable|%{$foundLicence+=$_.Feature}}# returns bulk lines of data
				}else{$foundLicence="No licences"}
				# Write-Log Error "FID $domain\$user-$foundRole has no licences in $domain\$env control room" $log_file;
				# If( ![string]::IsNullOrWhiteSpace($role) ){
					# if((Compare-Object -ReferenceObject $role -DifferenceObject $foundRole ) -eq $null){$roleMatch=$true}else{$roleMatch=$false}
				# }
				
					# If(![string]::IsNullOrWhiteSpace($licence)){
						# if((Compare-Object -ReferenceObject $licence -DifferenceObject $foundLicence) -eq $null){$licenceMatch=$true
						# }else{$licenceMatch=$false}
						# If( $roleMatch -and $licenceMatch){
							# Write-Log Success "FID $domain\$user has exact same roles and licences" $log_file 
							# $connection.close() ; return $true
						# }elseif(![string]::IsNullOrWhiteSpace($role) -or ![string]::IsNullOrWhiteSpace($licence)){
							# $foundRole|%{$fr+=($_+",")} ; $foundLicence|%{$fl+=($_+",")}
							# Write-Log Warning "FID $domain\$user-$foundRole-$foundLicence-not exactly match the given values." $log_file 
						# }
					# }elseif($roleMatch){}
			# }else{Write-Log Error "FID $domain\$user has no roles in $domain\$env control room" $log_file}
		}else{Write-Error "FID $domain\$user was not found in $domain\$env control room!"}
		$connection.close() ; return "$domain\$user-$foundRole-$foundLicence"
	}
	# Development
	# FuncRuntime
	# FuncRuntime,MetabotRuntime
}

Function Backup-JavaFiles($javaReg,$oldFile,$log_file="$home\Documents\WindowsPowerShell\log.txt"){ # to be updated
	$str = switch -regex ($oldFile){
		"jar$"        {"lib\ext";break}
		"dll$"        {"bin";    break}
		"properties$" {"lib";         }
		}
	$oldFile =$javaReg.InstallLocation + "$str\$oldFile"
	try{
		if(test-path $oldFile){rename-item $oldFile "_"+$oldFile -force}
		else{Write-Log Error "Unable to find the file $oldFile" $log_file}
	}catch{
		Write-Log Error "Unable to rename the java file" $log_file
	}finally{}
}
Function Copy-AAFileToJavaFolder($AAReg,$javaReg,$file,$log_file="$home\Documents\WindowsPowerShell\log.txt"){ # to be updated
	$sourcePath = $AAReg.InstallLocation+$file
	$targetpath = $javaReg.InstallLocation
	if(test-path $AAReg.InstallLocation){
		$str=switch -regex ($file){
			"jar$"        {"lib\ext";break}
			"dll$"        {"bin"    ;break}
			"properties$" {"lib"          }
		}
		copy-item -force $sourcePath "$targetpath\$str"
	}else{Write-Log Error "Unable to find the file $oldFile" $log_file}
}
Function Add-EnvPath($computer,$path,$log_file="$home\Documents\WindowsPowerShell\log.txt"){ # to be updated
	if($env:path -like "*$path*" ){
		Write-Log Normal "The path: $path is already in the env Path in $($env:computername)" $log_file
	}else{
		if(test-path $path -ea 0){
			$NewEnvpath = $env:path + ";" + $path
			Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" -Name PATH -Value $NewEnvpath
		}else{
			Write-Log Error "The $path doesn't exist in $env:computername" $log_file
		}
	}
}
Function Clear-JavaCahe(){
	$jDeploy="$home\AppData\LocalLow\Sun\Java\Deployment"
	try{
		"$jDeploy\cache\6.0","$jDeploy\ext","$jDeploy\log"|%{del "$_\*" -force -ea 0}
	}catch{}finally{}
}
Function Set-DefaultBrowser($defaultBrowser="ie"){
    $regKey= "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\{0}"
    $regKeyFtp= $regKey -f 'ftp' ;$regKeyHttp   = $regKey -f 'http' ;$regKeyHttps  = $regKey -f 'https'
    $regKeyFtp,$regKeyHttp,$regKeyHttps|%{ # check if the registry key/value exist and then create it 
        $path=$_
        If( !(test-path $path -ea 0) ){
            new-item -path $path | Out-Null
            new-item -path $path\UserChoice | Out-Null
        }
    }
    $regValuFtp,$regValuHttp,$regValuHttps=switch -Regex ($defaultBrowser){
        'ie|internet|explorer'  {@("IE.FTP","IE.HTTP","IE.HTTPS")               ; break} # Internet Explorer
        'ff|firefox'            {@("FirefoxURL","FirefoxURL","FirefoxURL")      ; break} # Firefox
        'cr|google|chrome'      {@("ChromeHTML","ChromeHTML","ChromeHTML")      ; break} # Google Chrome
        'sa*|apple'             {@("SafariURL","SafariURL","SafariURL")         ; break} # Safari
        'op*'                   {@("Opera.Protocol","Opera.Protocol","Opera.Protocol") } # Opera
    } 
    "$regKeyFtp-$regValuFtp","$regKeyHttp-$regValuHttp","$regKeyHttps-$regValuHttps"|%{Set-ItemProperty ("$($_.split('-')[0])\UserChoice") -name ProgId $_.split('-')[1]}
}
Function Move-Cursor($x,$y){
    [void][System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
	[windows.forms.cursor]::Position = New-Object System.Drawing.Point($x,$y)
}
Function Wait-ElementTo(){# to be updated
	param(
		[Parameter(Mandatory=$true)]
		[__ComObject]$ie,
		[Parameter(Mandatory=$false,ParameterSetName="id")]
		[string]$id,
		[Parameter(Mandatory=$false,ParameterSetName="tag")]
		[string]$name,
		[Parameter(Mandatory=$true,ParameterSetName="tag")]
		[string]$tag,
		[Parameter(Mandatory=$false)]
		[ValidateSet("Appear", "Disappear")]
		[string]$action="Appear"
	)
	$cnt=0
	If($PSBoundParameters.ContainsKey('id')){
		Try{
			$element = $ie.Document.IHTMLDocument3_getElementByID($id)
			Switch($action){
				"Appear"{$cmdlets="$element.GetType().name -ne '__ComObject'"}
				"Disappear"{$cmdlets="$element.GetType().name -eq '__ComObject'"}
			}
			while(iex $cmdlets){
				Start-Sleep -Milliseconds 200;$cnt++
				If($cnt -ge 50){
					Write-Log Error "Wait too long (10s) for the element $action in IE page" $log_file
					return $false
				}
				$element = $ie.Document.IHTMLDocument3_getElementByID($id)
			}
			return $true
		}Catch{
		    If( $action -ieq "Disappear" ){	return $true }
		}Finally{}
	}elseif($PSBoundParameters.ContainsKey('name')){
		do{
			$foundEL=$false ; $elements=$ie.Document.IHTMLDocument3_getElementsByTagName($tag)
			foreach($e in $elements){
				Switch($action){
					"Appear"{if($e.innertext -eq $name){return $true}else{$foundEL= $true}}
					"Disappear"{if($e.innertext -eq $name){$foundEL= $true ; break}}
				}
			}
			Start-Sleep -Milliseconds 200 ; $cnt++
			If($cnt -ge 50){
				Write-Log Error "Wait too long (10s) for the element $action in IE page" $log_file
				return $false
			}
		}while($foundEL)
		return $true
	}
}
Function Wait-CRUserCreatePageLoad(){# to be updated
	param(
		[Parameter(Mandatory=$True)]
		[__ComObject]$ie,
		[Parameter(Mandatory=$False)]
		[string]$elName="User Management",
		[string]$crUrl,
		[string]$log_file="$home\Documents\WindowsPowerShell\cr_log.txt"
	)
    $ie.Navigate($crUrl) ; while ($ie.Busy -or $ie.ReadyState -ne 4) { Start-Sleep -Milliseconds 200}
	Active-Window $ie.LocationName $log_file 10 1>$null; Wait-ElementTo -ie $ie -name $elName -tag "span" 1>$null; 
	Active-Window $ie.LocationName $log_file 10 1>$null
	1..8|%{Start-Sleep -Milliseconds 200;send-keys "tab"} ; send-keys "{DOWN 3}" ; send-keys "{TAB 2}" ; send-keys "enter" ; send-keys "enter"
	return $true
}

Function Start-BulkThreads(){	# -Action { Stop-Process -Id $PID } ----------------------> to be updated
	# useing parallel threads to run scripts in the same time so that it will speedup the running duration
	#reference https://www.codeproject.com/tips/895840/multi-threaded-powershell-cookbook
    param(
    	[Parameter(Mandatory=$True)]
    	[Object[]]$vdilist,             # the vdi list that scripts to be ran
    	[ScriptBlock]$Action,           # the actual action that each thread will run
    	[AllowNull()]
    	[Object[]]$ActionParams,        # the parameters that each thread needs, this can be a single or array of parameter
    	[Parameter(Mandatory=$false)]   
    	[int]$max_threads_count=100,    # the default amount of powershell threads, if this number is big, then the system might get stuck
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
    $total_thread_return_result = New-Object System.Collections.ArrayList
	If( $vdilist.Count -eq 1 ){ # no need to create thread pool to run it
		If( [string]::IsNullOrWhiteSpace($vdilist[0]) ){
		    write-log Error "vdilist only contains 1 line but it's null" $log_file
		}else{
            Try{
                [void]$total_thread_return_result.Add((& $Action $vdilist[0] 1 $ActionParams))
            }Catch{
                write-log Error $_.Exception.Message $log_file
            }Finally{}
        }
	}else{
		If( $vdilist.Count -le $max_threads_count ){ $max_threads_count = $vdilist.Count }
		# creaate the thread pool and run each thread based the vdi list amount
		$SessionState = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
		$Pool = [runspacefactory]::CreateRunspacePool(1, $max_threads_count, $SessionState, $Host)
		$Pool.Open() ; $threads = New-Object System.Collections.ArrayList ; $cnt=1
        $handles = @()
		foreach($line in $vdilist){
			if( [string]::IsNullOrWhiteSpace($line ) ){continue}
            write-verbose "Starting to create thread $cnt with param <<$line>>" -verbose
			$powershell = [powershell]::Create().AddScript($Action).AddArgument($line).AddArgument($cnt).AddArgument($ActionParams)
			$powershell.RunspacePool = $Pool ; $handles+=$powershell.BeginInvoke() ; [void]$threads.Add($powershell) ; $cnt++
		}
        $finish_cnt=0 ; $total_cnt=$handles.count
		do{ $done = $true # wait each thread to be done and then collect its return value
            $complete_percent=[math]::round(($finish_cnt / $total_cnt * 100),2)
            Write-Progress -Activity "Remaining $($total_cnt - $finish_cnt) of $total_cnt threads running" -PercentComplete $complete_percent -Status "$complete_percent completed" 
            For( $i=0; $i -lt $handles.count; $i++ ){
                if($handles[$i] -ne $null){
                    if($handles[$i].IsCompleted){
                        write-verbose ("The thread $($i+1) with param <<{0}>> has been completed" -f $vdilist[$i]) -verbose
                        If( ($single_thread_result=$threads[$i].EndInvoke($handles[$i])) -ne $null){ # return PSDataCollection<PSObject>
                            $single_thread_result.ReadAll()|%{[void]$total_thread_return_result.Add($_)}
                        }
                        $threads[$i].Dispose(); $threads[$i] = $null; $handles[$i]=$null ; $finish_cnt++
                    }else{$done = $false}
                }  
            }
            Start-Sleep -Milliseconds 200
		} until ($done)
        $threads=$null ; $handles=$null
        'threads','handles'|%{Remove-Variable $_ -Force -ea 0}
        [void]$Pool.close()  ; [void]$Pool.Dispose()
	}
    return $total_thread_return_result
}
Function Copy-File(){ # to be updated
	[CmdletBinding()]param(
		[Parameter(Mandatory=$True)]
		[string]$vdi_list_file,
		[string]$source_file_list,
		[Parameter(Mandatory=$false)]
		[string]$end_folder="c$\temp\"
	)
	$copy_action={
		param($vdi,$cnt,$action_params)
		$source_file_list,$end_folder=$action_params
		Try{
			Foreach( $f in $source_file_list){
				write-host "copy $f to $cnt-$vdi ..."
				copy $f \\$vdi\$end_folder -force
			}
		}Catch{
		    $_
		}Finally{}
	}
	$vdi_list=(get-content $vdi_list_file |select -Unique|?{$_.trim() -ne ""}|sort)
	Start-BulkThreads $vdi_list $copy_action $source_file_list,$end_folder
}

Function New-ScheduledTask(){
    #This function is used because when using SCHTASKS to create the task, the single quote ' will always be force transferred to be double quote "
    # paramters using
    # $task_arg="-WindowStyle Hidden -Executionpolicy unrestricted `"test.ps1 `'C:\Program Files (x86)\Java`'`""
    # -run_task_user 'System'                                                          ==> use system account to run this task
    # -run_task_user 'System' -runas                                                   ==> use system account to run this task with highest privileges
    # -run_task_user 'nam\aaadmin' -run_task_user_pwd 'xxxxxxx' -run_login_only        ==> use specified account to run this task
    # -run_task_user 'nam\aaadmin' -run_task_user_pwd 'xxxxxxx' -run_login_only -runas ==> use specified account to run this task with highest privileges
    # -task_arg "-noexit -Executionpolicy unrestricted `"c:\temp\test.ps1 `'C:\Program Files (x86)\Java`'`"" -run_task_user 'nam\cw50406' -run_login_only
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$False)]                  
        [string]$run_task_vdi="localhost",                      # the vdi to be ran the task
        [string]$task_name='temp_task',                         # the task name to be created and invoked later
        [string]$run_task_user='System',                        # the user who will run the task, default is System account
        [object]$run_task_user_pwd=$null,                       # the password for user running, default is null for system or login running only tpye
        [string]$task_executor= "powershell.exe",               # the main task file or exe to be ran
        [AllowNull()][string]$task_arg,                         # the arguments and parameters for the $task_command
        $log_file="$home\Documents\WindowsPowerShell\log.txt",  # the log file for log info store
        [switch]$run_login_only,                                # run task only when the user has logged in the VDI, must be true for non-system account
        [switch]$runas                                          # run task with highest privileges
    )
    #<--------------------------------- create and connect remote VDI Schedule Service
    Try{
        $task_schedule_service = new-object -ComObject("Schedule.Service")
        Try{
            $task_schedule_service.Connect($run_task_vdi)
            #<--------------------------------- setup task definition
            $task_definition = $task_schedule_service.NewTask(0) 
            $task_definition.Settings.Enabled = $true
            $task_definition.Settings.AllowDemandStart = $true
            #<--------------------------------- setup actual task actions
            $task_action = $task_definition.Actions.Create(0)
            $task_action.Path = $task_executor
            $task_action.Arguments = $task_arg
            #<--------------------------------- setup task trigger to be once ran 
            $task_triggers = $task_definition.Triggers
            $task_trigger = $task_triggers.Create(1)
            $task_trigger.StartBoundary = "2100-01-01T00:00:00"
            # $task_trigger.ExecutionTimeLimit = "00:00:05" # or "PT5M" <# stop the task if it tuns longer than this value#>
            $task_trigger.Enabled = $true
            #<--------------------------------- register the task in remote VDI with given account(default is System account)
            Try{
                $root_folder = $task_schedule_service.GetFolder("\")
                $logon_type=Switch($run_login_only){$true{3;break}$false{5}}
                $root_folder.RegisterTaskDefinition($task_name,$task_definition,6,$run_task_user,$run_task_user_pwd,$logon_type) 1>$null
                If( $runas ){
                    $error.clear()
                    If( SCHTASKS /QUERY /S $run_task_vdi /TN $task_name 2>$null){ #<----------------------check if task has been created
                        $run_task_user_pwd|SCHTASKS /Change /S $run_task_vdi /RL HIGHEST /tn $task_name 1>$null 2>&1
                        #<----------------------check if task has been changed to be ran with highest privileges
                        If( (SCHTASKS /QUERY /S $run_task_vdi /TN $task_name /XML 2>$null) -imatch 'HighestAvailable'){
                            return $true
                        }else{
                            write-log Error $error $log_file ; return $false
                        }
                    }else{ write-log Error "Not found task $task_name to set its running as admin" $log_file ; return $false}
                }else{return $true}
            }Catch{write-log Error $_ $log_file ; return $false}Finally{}
        }Catch{write-log Error $_ $log_file ; return $false}Finally{}
    }Catch{write-log Error $_ $log_file ; return $false}Finally{}
}
Function Get-LogonHistory{
	[CmdletBinding()]Param(
		[Parameter(Mandatory=$false)]
		[string]$Computer = $env:computername,
		[Parameter(Mandatory=$false)]
		[int]$Days = 3
	)
	$Result = @()
	Write-Host "Gathering Event Logs on computer $Computer in $Days days, this can take awhile..."
	$ELogs = Get-EventLog System -Source Microsoft-Windows-WinLogon -After (Get-Date).AddDays(-$Days) -ComputerName $Computer
	If ($ELogs){ 
		Write-Host "Processing..."
		ForEach ($Log in $ELogs){
			If ($Log.InstanceId -eq 7001){ $ET = "Logon"
			}ElseIf ($Log.InstanceId -eq 7002){ $ET = "Logoff"
			}Else{ Continue}
			$Result += New-Object PSObject -Property @{
				Time = $Log.TimeWritten
				'Event Type' = $ET
				User = (New-Object System.Security.Principal.SecurityIdentifier $Log.ReplacementStrings[1]).Translate([System.Security.Principal.NTAccount])
			}
		}
		$Result=$Result | Select Time,"Event Type",User | Sort Time -Descending | Out-GridView
        return $Result
		Write-Host "Done."
	}Else{ 
		Write-Host "Problem with $Computer."
		Write-Host "If you see a 'Network Path not found' error, try starting the Remote Registry service on that computer."
		Write-Host "Or there are no logon/logoff events (XP requires auditing be turned on)"
	}
}
Function Get-Resolution([Switch]$all){
    # Get-WmiObject -Class Win32_DesktopMonitor | Select-Object ScreenWidth,ScreenHeight
    # (Get-WmiObject -Class Win32_VideoController).VideoModeDescription
    # Get-WmiObject -class "Win32_DisplayConfiguration"
    Add-Type -AssemblyName System.Windows.Forms
	$rst=[System.Windows.Forms.Screen]::AllScreens
	If( $all){
		$rst|%{
			If( $_.Primary -ieq "true" ){
				$primary="True"
			}else{
				$primary="False"
			}
			$_.Bounds|select @{n='Primary';e={$primary}},Width,Height
		}
	}else{
	    $rst|?{$_.Primary -ieq "true"}|%{$_.Bounds|select Width,Height}
	}
}
Function Get-RegistryKey( $regkey,$temp_csv_file,[switch]$recurse){ # to be optimized
	If( $regkey.Substring($regkey.Length-1) -eq "\"){$regkey=$regkey.Substring(0,$regkey.Length-1)}
	$regkey=$regkey `
		-replace("^HK(EY_)*CU(RRENT_USER)*:*","HKU:\$sid") `
		-replace("^HK(EY_)*L(OCAL_)*M(ACHINE)*:*","HKLM:") `
		-replace("^HKEY_USERS","HKU:") `
		-replace("^HKEY_LOCAL_MACHINE","HKLM:")
	If($regkey -inotmatch "International\\Scripts|internet explorer\\vbs|SearchScopes|Suggested Sites|TypedURLs|TypedURLsTime|Update.*DateTime|AboutURLs|FavoriteUrls|UrlTemplate|" ){
		Foreach( $pro in (get-item $regkey).Property){
			$value=[String]::join(",",(Get-ItemPropertyValue $regkey $pro))
			$csv_regkey=$regkey.replace("HKU:\$sid","HKCU:")
			"`"IE`",`"$csv_regkey\$pro`",`"$value`"" |Out-File -FilePath $temp_csv_file -Append -Encoding utf8 -Force
		}
        If( $recurse ){
            Foreach( $key in (dir -LiteralPath $regkey).name){
                Get-RegistryKey $key $temp_csv_file -recurse
            }
        }
	}
}
Function Get-IOSize (){ 
    [CmdletBinding()]param(
        [Parameter(Mandatory=$False)]
        [string]$path=$pwd,
        [string]$size_unit='MB',
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
    # method 1:  to be updated , seems must be running as admin
    # [string]$result = robocopy /b /l /mir $directory $env:TEMP /r:0 /w:0 /ns /nc /nfl /ndl /njh /bytes
    # if (!($lastexitcode -eq 16)){
    # $pos = ($result).indexof("Bytes : ") # 字节
    # $start = $pos + 8
    # $length = $result.length
    # $end = $length - $start
    # $newstring = ($result).substring($start,$end)
    # $newstring = $newstring.trim()
    # $newstring -imatch "Bytes : .*? " 1>$null

    # [int64]($Matches[0].split(':')[-1].trim())/1gb
    # }else {
        # return $null
    # }
    # method 2:
    Try{
        If( (Get-Item $path -ea 1).psiscontainer ){
            $method='GetFolder'
        }else{
            $method='GetFile'
        }
        $objFSO = New-Object -ComObject Scripting.FileSystemObject
        $io_size= $objFSO.$method($path).Size
    }Catch{
        write-log Error $_.Exception.Message $log_file
        return -1
    }
    return ("{0:N2} $size_unit" -f ($io_size/"1$size_unit"))
}
Function Show-DeskTop(){
    (New-Object -ComObject shell.application).toggleDesktop()
}
Function Show-Window(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$False,ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [int]$hwnd = (Get-NewIEProcess $old_ie_process|?{$_.MainWindowTitle -ne ""}).MainWindowHandle
    )
    # $int
    $sig = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
    Add-Type -MemberDefinition $sig -name NativeMethods -namespace Win32
    [Win32.NativeMethods]::ShowWindowAsync($hwnd, 2) 1>$nul ;sleep 1 # Minimize window
    # [Win32.NativeMethods]::ShowWindowAsync($hwnd, 3) 1>$nul ;sleep 1 # Maximize window
    [Win32.NativeMethods]::ShowWindowAsync($hwnd, 4) 1>$nul # Restore window
}
Function Show-MstscWindow(){
    [CmdletBinding()]param(
        [string]$vdi,
        [ValidateSet("Minimize","Maximize","Restore")]
        [String]$WindowStyle="Maximize"
    )
    If( $hwnd = (ps mstsc|?{$_.MainWindowTitle -imatch $vdi}).MainWindowHandle ){
        $int = Switch($WindowStyle){
            "Minimize" {2 ; break}
            "Maximize" {3 ; break}
            "Restore"  {4}
        }
        $sig = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
        Add-Type -MemberDefinition $sig -name NativeMethods -namespace Win32
        [Win32.NativeMethods]::ShowWindowAsync($hwnd, $int) 1>$nul
        If(4 -ne $int){
            [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic")
            [Microsoft.VisualBasic.Interaction]::AppActivate("$vdi - Remote Desktop Connection")
        }
    }else{write-host "Not found mstsc process with '$vdi' in the window title"}
}
Function Lock-WorkStation(){
    $signature = @"
    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool LockWorkStation();
"@
    $name = "Win32LockWorkStation"
    $LockWorkStation = Add-Type -memberDefinition $signature -name $name -namespace Win32Functions -passthru
    $LockWorkStation::LockWorkStation() | Out-Null
}
Function Get-MapDrive(){
    Get-WmiObject win32_logicaldisk -Filter 'drivetype=4'|select DeviceID,ProviderName, @{n="Size(G)";e={"{0:N2}" -f ($_.Size/1gb)}}, @{n="Free(G)";e={"{0:N2}" -f ($_.FreeSpace/1gb)}}
}
Function Switch-ForcePasswordEnter(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$False)]
        [string]$key='HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services',
        [ValidateSet("enable","disable")]
        [string]$action='disable',
        [string]$log_file
    )
    <# 
        it's found that if below registry has been set to value 1, then the mstsc automatical login will not work(with this error message:'The server's authetication policy does not allow connection requests using saved credentials. Please enter new credetails.), so we have to check it and reset it to value 0 during this script running and then reset it to original value after script finishes.

        [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services]
        "fPromptForPassword"=dword:00000001
    #>
    $check_value,$new_value=Switch($action){
        "disable"{1,0}
        "enable"{0,1}
    }
    if($check_value -eq ((Get-ItemProperty $key -Name fpromptforpassword -ea 0).fpromptforpassword)){
        Try{
            Set-ItemProperty $key -Name fpromptforpassword -Value $new_value -Force -ea 1
            # below command will updated the group policy which should run before remove login ok button , as this will reset registry back
            gpupdate /force 1>$null 2>&1 # if the value has been modified successfully, update the goup policy so this change will take effect
            While( ps gpupdate -ea 0 ){ sleep 1}
            If( $new_value -eq ((Get-ItemProperty $key -Name fpromptforpassword -ea 0).fpromptforpassword) ){ 
            write-log Success "Have set fpromptforpassword value to $new_value" $log_file ; return $true }
        }Catch{
            write-log Error $_.Exception.Message $log_file
        }Finally{}
    }else{
        write-log Normal "Either value of key fpromptforpassword is already $new_value or this key does not exist at all." $log_file
    }
}
Function New-Shortcut(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$shortcut_path,
        [string]$target_path
    )
    $WshShell = New-Object -comObject WScript.Shell
    $shortcut = $WshShell.CreateShortcut($shortcut_path)
    $shortcut.TargetPath = $target_path
    $shortcut.Save()
}
Function Validate-PCCredential(){ # check if the given local pc credential is valid , so far unauthorized user is not in concern 
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$fid,
        [string]$pwd,
        [Parameter(Mandatory=$False)]
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
    $sec_pwd=ConvertTo-SecureString $pwd -AsPlainText -Force
    $creds = New-Object System.Management.Automation.PSCredential ($fid, $sec_pwd)
    try{ # if start bulk process PING at same time, it will get this error: System.InvalidOperationException: This command cannot be run due to the error: An instance of the service is already running.
   # at System.Management.Automation.MshCommandRuntime.ThrowTerminatingError(ErrorRecord errorRecord)
        start notepad -ArgumentList ($tf=(New-TemporaryFile).FullName) -Credential $creds -WindowStyle Hidden -ea 1
        (gwmi win32_process|?{$_.CommandLine -imatch [regex]::Escape($tf)}).Terminate()
        del $tf -force -ea 4 ; return $true
    } catch {
        write-log Error ("Validate Credential: {0}" -f $_) $log_file ; return $false
    }
}
Function Validate-ADCredential(){ # check if the given fid credential is valid , so far unauthorized user is not in concern 
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [ValidatePattern("^(nam|apac|eur)\\.{7,9}")]
        [string]$ad_user,
        [string]$ad_pwd,
        [Parameter(Mandatory=$False)]
        [string]$log_file="c:\temp\log.txt"
    )
    try{
        If( $ad_user -eq (New-Object DirectoryServices.DirectoryEntry("",$ad_user,$ad_pwd)).username ){return $true}else{return $false}
    } catch {
        write-log Error ("Validate Credential: {0}" -f $_) $log_file ; return $false
    }
}
Function Get-DSTInfo([Parameter(Mandatory=$true)][AllowNull()][string]$log_file){ # this fucntion is used to get this year winter time function date
    Process{ # this fucntion needs to be optimized for some bugs
        Try{
            $TimeZone = Get-WmiObject -Class Win32_TimeZone
            $STNDHour = $TimeZone.StandardHour
            $STNDMonth = Switch ($TimeZone.StandardMonth){
                1  {"January"}
                2  {"February"}
                3  {"March"}
                4  {"April"}
                5  {"May"}
                6  {"June"}
                7  {"July"}
                8  {"August"}
                9  {"September"}
                10 {"October"}
                11 {"November"}
                12 {"December"}
            }
            [DateTime]$SDate = "$STNDMonth 01, $((Get-Date).Year) $STNDHour`:00:00"
            $i = 0
            While ($i -lt $TimeZone.StandardDay){
                If ($SDate.DayOfWeek -eq $TimeZone.StandardDayOfWeek){ $i++
                    If ($i -eq $TimeZone.StandardDay){
                        $SFinalDate = $SDate
                    }Else{
                        $SDate = $SDate.AddDays(1)
                    }
                }Else{
                    $SDate = $SDate.AddDays(1)
                }
            }
            If ($SFinalDate.Month -ne $TimeZone.StandardMonth){
               $SFinalDate = $SFinalDate.AddDays(-7)
            }
           return $SFinalDate
        }Catch{
            If($null -ne $log_file ){
                write-log Error $Error[0].Exception.Message $log_file
            }else{
                $_
            }
            return [datetime]::Now.AddDays(1)
        }
    }
}
Function Get-ShortcutTargetpath(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$shortcut
    )
    $wshell = New-Object -ComObject WScript.Shell 
    $shortcut_pro=$wshell.CreateShortcut($shortcut)
    return $shortcut_pro.TargetPath
}
Function Get-ProcessUsage(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$False)]
        [string]$process="powershell",
        [int]$cpu_cores= (Get-WMIObject Win32_ComputerSystem).NumberOfLogicalProcessors,
        [AllowNull()][Double]$max_cpu_usage,
        [AllowNull()][Double]$max_memory_usage,
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
    $current_cpu_usage=[math]::Round((((Get-Counter "\Process($process*)\% Processor Time" -ea 0).CounterSamples.CookedValue|Measure-Object -Sum).Sum / $cpu_cores), 2)
    $current_memory_usage = ((ps "$process*" -ea 0).WS|Measure-Object -Sum).Sum / 1kb
    write-log Normal "$process Current CPU Usage(%): $current_cpu_usage | Memory(KB): $current_memory_usage" $log_file
    If( $current_cpu_usage -gt $max_cpu_usage ){ $max_cpu_usage=$current_cpu_usage }
    If( $current_memory_usage -gt $max_memory_usage ){ $max_memory_usage=$current_memory_usage }
    return $max_cpu_usage,$max_memory_usage
}
Function Empty-RecycleBin(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$False)]
        [string]$computer=$env:COMPUTERNAME,
        [ValidateSet('apac','eur','nam')]
        [string]$domain=$env:USERDOMAIN,
        [ValidatePattern("(?# user name must begin with letter and length bettwen 7 and 10)^\w{7,10}$")]
        [string]$fid=$env:USERNAME,
        [string]$log_file,
        [switch]$ClearAll
    )
    $recycle_bin_path="\\$computer\c$\`$recycle.bin" # the core code for this function work
    if(!$ClearAll){
        If( $computer -ne $env:COMPUTERNAME -and (!$PSBoundParameters.ContainsKey('domain') -or !$PSBoundParameters.ContainsKey('fid'))){
            write-Error "No domain or fid was provided for $computer"
        }
        $recycle_bin_path= join-path $recycle_bin_path (Get-SID $domain $fid)
        $info=" user($domain\$fid)"
    }
    Try{
        del $recycle_bin_path -Recurse -Force -ea 1
    }Catch [System.Management.Automation.ItemNotFoundException]{
        write-host "The$info RecycleBin is already empty in $computer"
    }Catch{
        $_
    }Finally{}
}
Function Invoke-HidenPowershell(){ # to be updated, for reference so far
    # ' When a scheduled task runs a PowerShell script in the end user context the console window is visible. Even with the -WindowStyle Hidden flag the console window will be visible for a second or so.
    # ' VB scripts can be run in the user context with no visible window at all. When a VB script calls PowerShell.exe with -WindowStyle Hidden the brief console pop up is completely suppressed.
    # ' reference link http://blog.buktenica.com/run-a-powershell-task-silently/
    # Set objShell = CreateObject("Wscript.Shell")
    # Set oFSO = CreateObject("Scripting.FileSystemObject")
    # this_path = oFSO.GetParentFolderName(WScript.ScriptFullName)
    # script="powershell.exe -WindowStyle Hidden -Executionpolicy Unrestricted -NonInteractive -File " & this_path & "\Close-AAMainWindow-Run-In-Remote.ps1"
    # Set myRegExp = New RegExp : myRegExp.Pattern = "\s"
    # For each arg in WScript.Arguments
        # if myRegExp.Test(arg) = "True" then arg=chr(34) & arg & chr(34) 'chr(34) is double quote
        # script=script+(" " & arg)
    # next
    # objShell.Run(script),0
}
Function Get-FileEncoding(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [ValidateScript({test-path $_})]
        [string]$file
    )
    ## First, check if the file is binary. That is, if the first 5 lines contain any non-printable characters.
    $non_printable = [char[]] (0..8 + 10..31 + 127 + 129 + 141 + 143 + 144 + 157)
    $lines = Get-Content $file -ErrorAction Ignore -TotalCount 5
    $result = @($lines | Where-Object { $_.IndexOfAny($non_printable) -ge 0 })
    if($result.Count -gt 0) { return "Binary" }
    ## Next, check if it matches a well-known encoding.
    ## The hashtable used to store our mapping of encoding bytes to their name. For example, "255-254 = Unicode"
    $encodings = @{}
    ## Find all of the encodings understood by the .NET Framework. For each,
    ## determine the bytes at the start of the file (the preamble) that the .NET
    ## Framework uses to identify that encoding.
    foreach($encoding in [System.Text.Encoding]::GetEncodings()) {
        $preamble = $encoding.GetEncoding().GetPreamble()
        if($preamble) {
            $encodingBytes = $preamble -join '-'
            $encodings[$encodingBytes] = $encoding.GetEncoding()
        }
    }
    ## Find out the lengths of all of the preambles.
    $encodingLengths = $encodings.Keys | ? { $_ } | % { ($_ -split "-").Count }
    # Go through each of the possible preamble lengths, read that many bytes from the file
    # and then see if it matches one of the encodings we know about.
    foreach($encodingLength in $encodingLengths | Sort -Descending) {
        $bytes = Get-Content -encoding byte -readcount $encodingLength $file | Select -First 1
        $encoding = $encodings[$bytes -join '-']
        ## If we found an encoding that had the same preamble bytes, save that output and break.
        If($encoding) { return $encoding }
    }
    return [System.Text.Encoding]::UTF7 # Assume the encoding is UTF7 by default
}
Function Start-Mstsc(){ # to be updated
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$fid_name,
        [Parameter(Mandatory=$False)]
        [string]$this_computer=$env:computername,
        [string]$this_path=$PSScriptRoot
    )
    Write-Log Normal "Checking if $fid_name has logged in $this_computer" $log_file
	If( ((quser $fid_name 2>$null ) -inotmatch 'USERNAME') -inotmatch 'Disc'){ # check if user session exists and not disconnet
		Write-Log Normal "User $fid_name has already logged in $this_computer" $log_file
	}else{
        Write-Log Error "Not found logged user '$fid_name', trying to get $fid_name password" $log_file
        [void][System.Reflection.Assembly]::Load([io.file]::ReadAllBytes("$this_path\CredentialValidate.dll"))
        If( ($fid_pwd=[CredentialValidate.GetPassword]::ReturnLoginPWD()) -eq $null ){
            Stop-WithError $action_type $run_rdc_user ": Not get $run_rdc_user password" $log_file ; exit
        }elseif(! (Validate-ADCredential "$run_rdc_user_domain\$run_rdc_user" $run_rdc_user_pwd $log_file )){
            Stop-WithError $action_type $run_rdc_user ": Invalid $run_rdc_user password!" $log_file ; exit
        }
        try{ # remove 'force prompt for password enter' & login ok button
            # somehow we found the mstsc will popup this error "The remote computer could not be authenticated due to problems with its security certificate. It may be unsafe to proceed.", and it can be bypassed by changing below registry key value
            Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Terminal Server Client" -Name AuthenticationLevelOverride -Value 0 -Force -ea 1
            $able_to_goto_next_step,$need_to_restore_fpromptforpassword=Switch-ForcePasswordEnter 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services' 'fpromptforpassword' 'disable' $log_file
            if(!$able_to_goto_next_step){ Stop-WithError $action_type $run_rdc_user ": Registry Modify Error for fpromptforpassword" $log_file ; exit }
            write-log Normal 'Trying to remove login ok button' $log_file
            "legalnoticecaption","legalnoticetext"|%{Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\policies\system\" -Name $_ -Value '' -Force -ea 1}
        }catch{ Stop-WithError $action_type $run_rdc_user (": Unhandled Error-->" + $_.Exception.Message) $log_file ; exit }
		try{ # move the rdcmon.bat in startup folder and move it back when the script finishes
			Move-Item "C:\Users\$run_rdc_user\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\rdcmon.bat" "$this_path\" -force 1>$null 2>&1
            write-log Normal "Trying to login $this_computer in user $run_rdc_user session" $log_file
            cmdkey.exe /generic:$this_computer /user:$run_rdc_user_domain\$run_rdc_user /pass:$run_rdc_user_pwd 1>$null 2>&1  # add credentils
			mstsc.exe /v $this_computer 1>$null 2>&1 ; $check_run_rdc_user_session_loop_count=0 ; $need_to_close_this_mstsc=$true 
			Do{ # loop to find if $run_rdc_user has logged in $this_computer
				$rst=((quser $run_rdc_user 2>$null ) -inotmatch 'USERNAME') -inotmatch 'Disc' ; sleep 5
				If( ++$check_run_rdc_user_session_loop_count -gt 59 ){
					Stop-WithError $action_type $run_rdc_user ": Not found $run_rdc_user login $this_computer in 5mins" $log_file ; exit
				}
			}While([string]::IsNullOrWhiteSpace($rst))
			Write-Log Success "User $run_rdc_user has now logged in $this_computer" $log_file
            if($need_to_restore_fpromptforpassword){Switch-ForcePasswordEnter 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services' 'fpromptforpassword' 'enable' $log_file|out-null}
		}catch{Stop-WithError $action_type $run_rdc_user (": Unhandled Error-->"+ $_.Exception.Message) ; exit}
	}
}
Function Import-Certificate(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [String]$cert_path,
        [Parameter(Mandatory=$False)]
        [string]$cert_store_location="CurrentUser",
        [string]$cert_store_name="Root",
        [string]$cert_pwd=$null
    )
    # https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509certificate2(v=vs.110).aspx#Methods
    $cert_obj = new-object System.Security.Cryptography.X509Certificates.X509Certificate2    
    $cert_obj.Import($cert_path, $cert_pwd, "Exportable,UserKeySet")    
    # https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509store(v=vs.110).aspx#Methods
    $store = new-object System.Security.Cryptography.X509Certificates.X509Store($cert_store_name,$cert_store_location)    
    $store.open("MaxAllowed") # Open the X.509 certificate store for the highest access allowed.    
    $store.add($cert_obj)    
    $store.Dispose()
    $store.close()
    return $cert_obj
    # (dir Cert:\CurrentUser\root|select -First 1).rawdata
    # (dir Cert:\CurrentUser\root).Issuer|%{$_.split('=,')[1]}
    <#
    ca -> Specifies certificates in the Intermediate Certification Authorities store
    my -> Specifies certificates issued to the current user
    root -> Specifies certificates in the Trusted Root Certification Authorities store
    spc -> Specifies software publisher certificates
    user_created_store -> Specifies the name of a user-created certificate store

    CertUtil -user -delstore root dbad38d07777e0f5bf8650a7129e3f2bfdd6e004  # delete certificate file
    #>
}
Function Set-ExcelMacro(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$ExcelVersion,
        [Parameter(Mandatory=$false)]
        [ValidateSet("Enable","Disable","Reset")]
        [string]$Action="Reset",
        [int]$AccessVBOMValue,
        [int]$VBAWarningsValue
    )
    $excel_macro="HKCU:\Software\Microsoft\Office\$ExcelVersion\excel\Security"
    Try{
        If( "Reset" -eq $Action){
            If( $AccessVBOMValue -ne (Get-ItemProperty $excel_macro).AccessVBOM ){
                New-ItemProperty -Path $excel_macro -Name AccessVBOM -PropertyType DWORD  -Value $AccessVBOMValue -Force -ea 1| Out-Null
            }
            If( $VBAWarningsValue -ne (Get-ItemProperty $excel_macro).VBAWarnings ){
                New-ItemProperty -Path $excel_macro -Name VBAWarnings -PropertyType DWORD  -Value $VBAWarningsValue -Force -ea 1| Out-Null
            }
        }else{
            $set_value = Switch($Action){
                "Enable"{1}
                "Disable"{0}
            }
            write-host "$Action|$set_value"
            "AccessVBOM","VBAWarnings"|%{
                New-ItemProperty -Path $excel_macro -Name $_ -PropertyType DWORD  -Value $set_value -Force -ea 1| Out-Null
            }
        }
        return $true
    }Catch{
        $_
        return $false
    }
}
Function Get-UserConfirm(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$msg,
        [string]$title
    )
    $form = New-Object 'System.Windows.Forms.Form' ; $form.TopMost = $true ; $form.ShowInTaskbar=$true
    $answer = [System.Windows.Forms.MessageBox]::Show($form, $msg, $title, 4) #Display the message with YES/NO
    $form.Close() ; $form.Dispose() #Close & Dispose of parent form
    return $answer
}
Function Get-UserCredential(){
    # [CmdletBinding()]param(
        # [Parameter(Mandatory=$True)]
        # [string]$,
        # [Parameter(Mandatory=$False)]
        # [string]$
    # )
    Do{ $loop_continue=$false
        $cred = Get-Credential -UserName $env:USERNAME -Message "Please enter user name and password"
        If( !$cred ){
            $x=Get-UserConfirm "You have cancled credential enter, are you sure?" "Confirm No Mis Action"
        }elseif([string]::IsNullOrWhiteSpace( ($passwd=$cred.GetNetworkCredential().Password))){
            $x=Get-UserConfirm "You have not provided password, are you sure?" "Confirm Empty Password"
        }
        if('no' -eq $x){$loop_continue=$true}
    }While($loop_continue)
    If( !$cred ){
        write-log Error "Provided credential is null even after user confirmed, exit the script" $log_file -print_to_console; exit
    }
    return $cred
}
Function Get-Incident(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$number,
        $snsession
    )
    $body = @{
        sysparm_query="GOTOnumber=$number"
    }
    Try{
        return ((Invoke-WebRequest "https://servicemanagement.citigroup.net/api/now/v1/table/incident" -Method Get -Body $body -WebSession $snsession).Content|ConvertFrom-Json).result[0]
    }Catch{
        write-log Error ("$number--"+$_.Exception.Message) $log_file ; return $null
    }
}
Function Get-SNUrlRequest(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$url,
        $snsession
    )
    Try{
        return ((Invoke-WebRequest $url -Method Get -WebSession $snsession).Content|ConvertFrom-Json).result[0]
    }Catch{
        write-log Error $_.Exception.Message $log_file ; return $null
    }
}
Function Get-ConfigValue(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        $config_content,
        [string]$config_key,
        [Parameter(Mandatory=$False)]
        [string]$log_file,
        [string]$this_record_folder,
        [Switch]$WriteLog,
        [Switch]$Exit
    )
    Try{
        If( [string]::IsNullOrWhiteSpace(($config_value=($config_content -imatch $config_key).split('=')[-1]))){
            If($WriteLog){write-log Error "$config_key was not found in the config file content" $log_file}
            $config_value=$null
        }
    }Catch{
        If($WriteLog){
            write-log Error $_.Exception.Message $log_file
            write-log Error "Not found '$config_key' in config content, might be commented out" $log_file 
        }
        $config_value=$null
    }
    If(!$config_value -and $Exit ){ 
        write-log Normal "<----------------End of Script Import-RegSetting.ps1(IR)---------------->" $log_file 
        copy $log_file "$this_record_folder\import-regsetting.log" -force -ea 4 ; exit 
    }
    return $config_value
}
Function Set-UserLogin(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$vdi,
        [string]$fid,
        [string]$pswd,
        [ValidateSet("Maximized","Minimized","Normal",'Hidden')]
        [string]$WindowStyle='Hidden',
        [switch]$KillMstsc,
        [string]$log_file
    )
    try{
        If( $fid -ne (New-Object DirectoryServices.DirectoryEntry("",$fid,$pswd)).username ){return "Invalid pwd($pswd) for $fid to login $vdi"}
    } catch {
        write-log Error ($err="Validate password error: {0}" -f $_) $log_file ; return $err
    }
    # remove login OK button
    "legalnoticecaption","legalnoticetext"|%{reg delete "\\$vdi\HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\policies\system\" /v $_ /f 1>$null 2>&1}
    # add FID to "remote desktop users" if not in it
    If( $fid_name -inotin (Get-VDIGroupMember $vdi "remote desktop users" $log_file).Member){
        If( !(Set-VDIGroupMember $vdi "remote desktop users" 'add' $fid $log_file) ){
            return "access denied to login"
        }
    }
    # start to connect it with mstsc process
    Try{cmdkey.exe /generic:$vdi /user:$fid /pass:$pswd 1>$null}Catch{}Finally{}
    $psi=[System.Diagnostics.ProcessStartInfo]::new("C:\Windows\system32\mstsc.exe","/v $vdi /w 1376 /h 768") # FileName, Arguments
    $psi.WindowStyle=[System.Diagnostics.ProcessWindowStyle]::$WindowStyle
    $ps=[System.Diagnostics.Process]::Start($psi) ; $err = $null;  $continue=$false ; $ln=0 ; $fid_name=$fid.split('\')[-1]
    Do{ sleep 2 # check if FID session exists after trying to login VDI
        If( quser /server:$vdi 2>$null ){
            write-log Success "($ln)Found $fid session in $vdi" $log_file -print_to_console; $continue= $true; break
        }
        If( ++$ln -gt 59 ){
            write-log Error ($err="Timeout(120s) to login $vdi with $fid") $log_file  ; break
        }
    }While( $ln -le 60 )
    If( $continue ){ $loop_cnt=0 # check if FID fully login VDI , rather than in login windows such as "apply user policy" and so on
        write-log Normal "Waiting $fid fully login $vdi" $log_file
        Do{
            Try{
                If(gwmi win32_process -cn $vdi -Filter "name like '%winvnc_32%'"){
                    write-log Success "($loop_cnt)Found $fid fully login $vdi" $log_file -print_to_console;  break
                }
            }Catch{ }Finally{sleep 2}
            If( $loop_cnt -gt 59 ){
                write-log Error ($err="Timeout(120s) to login $vdi with $fid") $log_file -print_to_console; break
            }
        }While( ++$loop_cnt -le 60)
    }
    If($KillMstsc){$ps.Kill()} # delete temp file & exit the mstsc process
    return $err
}
Function Get-IECompatibilityViewHex(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [array]$websites
    )
    $delim_a='01000000'
    $time_stamp='0000000000000001'
    $delim_b='0C000000'
    Function Get-WebsiteHex(){
        [CmdletBinding()]param(
            [Parameter(Mandatory=$True)]
            [string]$website
        )
        $hex_length="{0:X2}00" -f $website.length
        $hex_data=([System.Text.Encoding]::Unicode.GetBytes($website)|%{"{0:X2}" -f $_}) -join ""
        return "$delim_b$time_stamp$delim_a$hex_length$hex_data"
    }
    $websites|%{$hex_website += Get-WebsiteHex $_}
    $hex_header='411F00005308ADBA'
    $hex_website_count="{0:X2}000000" -f $websites.count
    $hex_checksum='FFFFFFFF'
    return "$hex_header$hex_website_count$hex_checksum$delim_a$hex_website_count$hex_website"
    # reg add 'HKCU\Software\Microsoft\Internet Explorer\BrowserEmulation\ClearableListData' /v UserFilter /t REG_BINARY /d $x /f
    # iexplore "about:blank" 
}
Function Test-URIConnection{
    [cmdletbinding(DefaultParameterSetName="Default")]Param(
        [Parameter(Position=0,Mandatory,HelpMessage="Enter the URI path starting with HTTP or HTTPS",
        ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [ValidatePattern( "^(http|https)://" )]
        [Alias("url")]
        [string]$URI,
        [Parameter(ParameterSetName="Detail")]
        [Switch]$Detail,
        [ValidateScript({$_ -ge 0})]
        [int]$Timeout = 30
    )
    Begin {
        Write-Verbose -Message "Starting $($MyInvocation.Mycommand)" 
        Write-Verbose -message "Using parameter set $($PSCmdlet.ParameterSetName)" 
    } #close begin block
    Process {
        Write-Verbose -Message "Testing $uri"
        Try {
            #hash table of parameter values for Invoke-Webrequest
            $paramHash = @{
                UseBasicParsing = $True
                DisableKeepAlive = $True
                Uri = $uri
                Method = 'Head'
                ErrorAction = 'stop'
                TimeoutSec = $Timeout
            }
            $test = Invoke-WebRequest @paramHash
            if ($Detail) {
                $test.BaseResponse | Select ResponseURI,ContentLength,ContentType,LastModified, @{Name="Status";Expression={$Test.StatusCode}}
            }else {
                if ($test.statuscode -ne 200) {
                    #it is unlikely this code will ever run but just in case
                    Write-Verbose -Message "Failed to request $uri"
                    write-Verbose -message ($test | out-string)
                    return $False
                } else { return $True }
            } #else quiet
         
        }Catch {
            write-verbose -message $_.exception
            if ($Detail) {  #most likely the resource is 404
                $objProp = [ordered]@{
                    ResponseURI = $uri
                    ContentLength = $null
                    ContentType = $null
                    LastModified = $null
                    Status = 404
                }
                #write a matching custom object to the pipeline
                New-Object -TypeName psobject -Property $objProp

            } else { return $False }
        } #close Catch block
    } #close Process block
    End {
        Write-Verbose -Message "Ending $($MyInvocation.Mycommand)"
    } #close end block
} #close Test-URI Function
Function Enable-IEJsonView(){
    $r_ct1="HKCR\MIME\Database\Content Type"
    "application/json","application/x-json","text/json"|%{
        reg add "$r_ct1\$_" /v CLSID /t REG_SZ /d "{25336920-03F9-11cf-8FD0-00AA00686F13}" /f 1>$null 2>&1
        reg add "$r_ct1\$_" /v Encoding /t Reg_BINARY /d 08000000 /f 1>$null 2>&1
    }
    # New-PSDrive HKCR Registry HKEY_CLASSES_ROOT
    # $r_ct2="HKCR:\MIME\Database\Content Type"
    # "application/json","application/x-json","text/json"|%{
        # $key=(gi HKCR:).OpenSubKey("MIME\Database\Content Type", $true)
        # $key.CreateSubKey($_)
        # $key.Close()
        # New-ItemProperty -LiteralPath "$r_ct2\$_" -Name CLSID -Value "{25336920-03F9-11cf-8FD0-00AA00686F13}"|out-null
        # New-ItemProperty -LiteralPath "$r_ct2\$_" -Name Encoding -PropertyType Binary -Value ([byte[]](0x08,00,00,00))|out-null
    # }
}
Function Start-AAServices($log){
	$AAPath = "C:\Program Files (x86)\Automation Anywhere Enterprise 10.3\Client"
	$AAE = "Automation Anywhere Enterprise"
	try{#<----------------------------try to start AAAutoLoginService
		$AAALS = Get-Service -Name AAAutoLoginService ; if($AAALS.Status -ne "Running"){Start-Service $AAALS}
		Write-Log -logType Success "Have started AAAutoLoginService.." $log
	}catch{
		try{
			Start-NewService "AAAutoLoginService" "$AAPath\AAAutoLoginService.exe" "$AAE Auto Login Service" "Provides support for Auto Login functionality in $AAE Client"
		}catch{Write-Log -logType Error "Failed to start AAAutoLoginService.." $log ; return $false}finally{}
	}finally{}
	try{#<----------------------------try to start AAClientService
		$AACS = Get-Service -Name AAClientService ; if($AACS.Status -ne "Running"){Start-Service $AACS}
		Write-Log -logType Success "Have started AAClientService.." $log
	}catch{
		try{
			Start-NewService "AAClientService" "$AAPath\AAClientService.exe" "$AAE Auto Client Service" "Provides $AAE Client functionality"
		}catch{Write-Log -logType Error "Failed to start AAClientService.." $log ; return $false}finally{}
	}finally{}
	try{#<----------------------------try to start "AAESchedulerService"
		$AAESS = Get-Service -Name "AAESchedulerService" ; if($AAESS.Status -ne "Running"){Start-Service $AAESS}
		Write-Log -logType Success "Have started AAESchedulerService.." $log
	}catch{
		try{
			Start-NewService "AAESchedulerService" "$AAPath\AAESchedulerService.exe" "$AAE Auto Scheduler Service" "Provides $AAE Scheduler functionality"
		}catch{Write-Log -logType Error "Failed to start AAESchedulerService.." $log ; return $false}finally{}
	}finally{}
	return $true
}
Function Set-WindowState(){
    [CmdletBinding()]param(
        [ValidateSet('FORCEMINIMIZE', 'HIDE', 'MAXIMIZE', 'MINIMIZE', 'RESTORE', 
                     'SHOW', 'SHOWDEFAULT', 'SHOWMAXIMIZED', 'SHOWMINIMIZED', 
                     'SHOWMINNOACTIVE', 'SHOWNA', 'SHOWNOACTIVATE', 'SHOWNORMAL')]
        [Alias('Style')]
        [String] $State = 'SHOW',
        [Parameter(ValueFromPipelineByPropertyname='True')]
        [System.IntPtr] $MainWindowHandle = (Get-Process –id $pid).MainWindowHandle,
        [switch] $PassThru
    )
    BEGIN{
        $WindowStates = @{
            'FORCEMINIMIZE'   = 11
            'HIDE'            = 0
            'MAXIMIZE'        = 3
            'MINIMIZE'        = 6
            'RESTORE'         = 9
            'SHOW'            = 5
            'SHOWDEFAULT'     = 10
            'SHOWMAXIMIZED'   = 3
            'SHOWMINIMIZED'   = 2
            'SHOWMINNOACTIVE' = 7
            'SHOWNA'          = 8
            'SHOWNOACTIVATE'  = 4
            'SHOWNORMAL'      = 1
        }
        $Win32ShowWindowAsync = Add-Type –memberDefinition @"
    [DllImport("user32.dll")] 
    public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); 
"@ -name "Win32ShowWindowAsync" -namespace Win32Functions –passThru
    }
    PROCESS {
        $Win32ShowWindowAsync::ShowWindowAsync($MainWindowHandle, $WindowStates[$State]) | Out-Null
        Write-Verbose ("Set Window State on '{0}' to '{1}' " -f $MainWindowHandle, $State)

        if ($PassThru)
        {
            Write-Output $MainWindowHandle
        }
    }
    END { }
}
Function Get-ProcessUsage(){
    [CmdletBinding()]param(
        [string]$vdi=$env:COMPUTERNAME,
        [int]$cpu_cores = (gwmi Win32_ComputerSystem -cn $vdi).NumberOfLogicalProcessors,
        [string]$process='Mcshield',
        [string]$log_file
    )
    Try{
        $current_cpu_usage=[math]::Round((((Get-Counter "\Process($process*)\% Processor Time" -cn $vdi -ea 1).CounterSamples.CookedValue|Measure-Object -Sum).Sum / $cpu_cores), 2)
        write-host $current_cpu_usage
    }Catch{
        Write-Log Error $_.Exception.Message $log_file ; return $_.Exception.Message
    }
    return $current_cpu_usage
}
Function Get-FrequentHistory($number=5){
    $history=@{} # get 5 frequently used histoy command
    (get-history).CommandLine|%{
        If( $_ -in $history.Keys){
            $history.$_+=1
        }Else{
            $history.$_=1
        }
    }
    $latest_history=($history.GetEnumerator()|sort value -Descending|select -First $number).name -join ("`n")
}
Function Enable-RemoteService(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$vdi,
        [string]$service_name='RemoteRegistry'
    )
    Try{
        Get-Service $service_name -cn $vdi -ea 1|?{
            If( $_.StartType -eq 'Disabled' ){
                Set-Service $service_name -cn $vdi -StartupType Automatic -ea 1
            }
            If($_.Status -ieq "Stopped"){
                Set-Service $service_name -cn $vdi -Status Running -ea 1
            }
        }
        return $true
    }Catch{
        return $_
    }
}
Function Exit-GreenZone(){ 
#<-------------- not run script in greenzone time, reference WIKI https://cedt-confluence.nam.nsroot.net/confluence/display/169794A/AA+STE+Infrustructure+Green-Zone
# $SkipGreenZone 
    $run_time_day_of_week=(get-date).DayOfWeek ; $run_time_hour=(get-date).hour
    $this_domain=(Get-WmiObject Win32_ComputerSystem).Domain.Split(".")[0].ToUpper()
    Switch -regex ($this_domain){
        "nam|apac"{ If(('Saturday' -eq $run_time_day_of_week -and 21 -le $run_time_hour) -or 'Sunday' -eq $run_time_day_of_week){ exit } }
        "eur"{If(('Saturday' -eq $run_time_day_of_week -and 18 -le $run_time_hour) -or ( 'Sunday' -eq $run_time_day_of_week -and 4 -ge $run_time_hour)){ exit }}
    }
}
Function Maximize-MstscWindow(){
#(ps mstsc|?{$_.MainWindowTitle -imatch 'A8613PRICGC014'}).MainWindowHandle | % {Maximize-MstscWindow $_ -ShowInFront}
# (ps mstsc).MainWindowHandle |% {Maximize-MstscWindow $_}
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [int]$mstsc_handle,
        [Switch]$ShowInFront
    )
    Begin {
        $sig = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
        Add-Type -MemberDefinition $sig -name NativeMethods -namespace Win32
    }
    Process {
        [Win32.NativeMethods]::ShowWindowAsync($mstsc_handle, 3) 1>$nul
        If( $ShowInFront ){
            [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic")
            [Microsoft.VisualBasic.Interaction]::AppActivate((ps mstsc|?{$mstsc_handle -eq $_.MainWindowHandle}).MainWindowTitle)
        }
    }
    End { }
}
Function Import-UIAutomation(){
    [void][System.Reflection.Assembly]::LoadWithPartialName("UIAutomationClient")
    [void][System.Reflection.Assembly]::LoadWithPartialName("UIAutomationTypes")
    [void][System.Reflection.Assembly]::LoadWithPartialName("UIAutomationProvider")
    [void][System.Reflection.Assembly]::LoadWithPartialName("UIAutomationClientsideProviders")
}
Function Get-UIWindow(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [System.Diagnostics.Process]$process,
        [string]$log_file
    )
    # reference link https://msdn.microsoft.com/en-us/library/system.windows.automation(v=vs.110).aspx
    Try{
        $root_element = [Windows.Automation.AutomationElement]::RootElement
        $wapc = New-Object Windows.Automation.PropertyCondition([Windows.Automation.AutomationElement]::ProcessIdProperty, $process.Id)
        return $root_element.FindFirst([Windows.Automation.TreeScope]::Children, $wapc)
    }Catch{
        return $_
    }Finally{}
}
Function Get-UIElement(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True,ValueFromPipeline=$true)]
        [Windows.Automation.AutomationElement]$au_element,
        [Parameter(Mandatory=$True)]
        [string]$control_type,
        [Parameter(Mandatory=$True)]
        [string]$condition_value, # id, name, class
        [string]$log_file,
        [switch]$CheckClass,
        [switch]$CheckName
    )
    <#
        most of the class name differs when AA version changes, so we first use element ID and name to find it
        $text_class_pro = New-Object Windows.Automation.PropertyCondition([Windows.Automation.AutomationElement]::ClassNameProperty, $text_class)
        $text_name_pro = New-Object Windows.Automation.PropertyCondition([Windows.Automation.AutomationElement]::NameProperty, $text_name)
        return $au_element.FindAll([Windows.Automation.TreeScope]::Descendants, (New-Object Windows.Automation.AndCondition($text_class_pro, $text_name_pro)))
    #>
    Try{
        $control_type_pro = New-Object Windows.Automation.PropertyCondition([Windows.Automation.AutomationElement]::ControlTypeProperty, [System.Windows.Automation.ControlType]::$control_type)
        If( $CheckClass ){
            $2nd_cond = "ClassNameProperty"
        }ElseIf($CheckName){
            $2nd_cond = "NameProperty"
        }Else{
            $2nd_cond = "AutomationIdProperty"
        }
        $2nd_cond = New-Object Windows.Automation.PropertyCondition([Windows.Automation.AutomationElement]::$2nd_cond, $condition_value)
        return $au_element.FindAll([Windows.Automation.TreeScope]::Descendants, (New-Object Windows.Automation.AndCondition($control_type_pro, $2nd_cond)))
    }Catch{
        If( [string]::IsNullOrWhiteSpace($log_file ) ){
            $_
        }Else{
            Write-Log Error $_.Exception.Message $log_file
        }
    }
}
Function Set-UIText(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [Windows.Automation.AutomationElement]$au_element,
        [Parameter(Mandatory=$True)]
        [string]$text
    )
    $au_element.GetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern).SetValue($text)
}
Function Invoke-UIButton(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [Windows.Automation.AutomationElement]$au_element
    )
    $au_element.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern).Invoke()
}
Function Get-UISize(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [AutomationElement]$au_element
    )
    return $au_element.GetCurrentPropertyValue([System.Windows.Automation.AutomationElement]::BoundingRectangleProperty)
}
Function Get-WindowSize(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$window_title
    )
    $word = new-object -com 'word.application'
    $task_window=$word.tasks | ? { $_.visible -and $_.name -imatch $window_title } | select *
    $word.quit() ; [void][System.Runtime.Interopservices.Marshal]::ReleaseComObject($word) ; Remove-Variable word -ea 4
    return $task_window
}
function Encrypt-String($String, $Passphrase=$env:username, $salt=$env:computername, $init="AA_Password"){
	# Create a COM Object for RijndaelManaged Cryptography
	$r = new-Object System.Security.Cryptography.RijndaelManaged
	# Convert the Passphrase to UTF8 Bytes
	$pass = [Text.Encoding]::UTF8.GetBytes($Passphrase)
	# Convert the Salt to UTF Bytes
	$salt = [Text.Encoding]::UTF8.GetBytes($salt)

	# Create the Encryption Key using the passphrase, salt and SHA1 algorithm at 256 bits
	$r.Key = (new-Object Security.Cryptography.PasswordDeriveBytes $pass, $salt, "SHA1", 5).GetBytes(32) #256/8
	# Create the Intersecting Vector Cryptology Hash with the init
	$r.IV = (new-Object Security.Cryptography.SHA1Managed).ComputeHash( [Text.Encoding]::UTF8.GetBytes($init) )[0..15]
	
	# Starts the New Encryption using the Key and IV   
	$c = $r.CreateEncryptor()
	# Creates a MemoryStream to do the encryption in
	$ms = new-Object IO.MemoryStream
	# Creates the new Cryptology Stream --> Outputs to $MS or Memory Stream
	$cs = new-Object Security.Cryptography.CryptoStream $ms,$c,"Write"
	# Starts the new Cryptology Stream
	$sw = new-Object IO.StreamWriter $cs
	# Writes the string in the Cryptology Stream
	$sw.Write($String)
	# Stops the stream writer
	$sw.Close()
	# Stops the Cryptology Stream
	$cs.Close()
	# Stops writing to Memory
	$ms.Close()
	# Clears the IV and HASH from memory to prevent memory read attacks
	$r.Clear()
	# Takes the MemoryStream and puts it to an array
	[byte[]]$result = $ms.ToArray()
	# Converts the array from Base 64 to a string and returns
	return [Convert]::ToBase64String($result)
}
Function Get-RegistryValue(){ # Get-RegistryValue -vdi SWDINTVM29106 -SubKey SOFTWARE\Policies\Google\Chrome -Recurce -ExportToCsv
    [CmdletBinding()]param(
        [string]$vdi=$env:COMPUTERNAME,
        [ValidateSet("CurrentUser","LocalMachine","Users","ClassesRoot")]  # [Microsoft.Win32.RegistryHive].GetEnumNames()
        [string]$Hive='LocalMachine',
        [string]$SubKey,
        [AllowNull()]
        [string]$Name, # if not provided, then it will get all sub names and values in this subkey
        [switch]$Recurce, # when set to true, will get all subkeys/values under current key
		[switch]$ExportToCsv, # whether to export the result to csv file
		[string]$OutputCSV="$PSScriptRoot\Get-RegistryValue.csv", # the default csv file to save the result
		[string]$log_file="$PSScriptRoot\Get-RegistryValue.log"
    )
    Try{
        If( $env:COMPUTERNAME -eq $vdi ){ # open local or remote /registry
            $reg = [Microsoft.Win32.RegistryKey]::OpenBaseKey($Hive,[Microsoft.Win32.RegistryView]::Default)
        }Else{
            $reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Hive, $vdi)
        }
        $result = [pscustomobject]@{ # used to return single formated result, either export to csv or pass to pipe
            VDI=$vdi
            Hive=$Hive
            SubKey=$SubKey
            Name=$Name
            Data="N/A"
            'Type'="N/A"
        }
        $results= New-Object System.Collections.ArrayList # used to return all data when there are more records found
        If( $sub_key = $reg.OpenSubkey($SubKey) ){
            If( !$Name ){ # as no specific name is provided, we then get all sub names and values in this subkey
                Function Get-SubKey(){ 
                    [CmdletBinding()]param(
                        [System.Collections.ArrayList]$results,
                        [string]$SubKeyName,
                        $sub_key,
                        [boolean]$Recurce
                    )
                    $sub_key.GetValueNames()|%{
                        $this_result=[pscustomobject]@{
                            VDI=$vdi
                            Hive=$Hive
                            SubKey = $SubKeyName
                            Name=$_
                            Data=$sub_key.GetValue($_)
                            'Type'=$sub_key.GetValueKind($_)
                        }
                        [void]$results.Add($this_result)
                    }
                    If( $Recurce ){
                        $sub_key.GetSubKeyNames()|%{
                            Get-SubKey $results "$SubKeyName\$_" $sub_key.OpenSubkey($_) $Recurce
                        }
                    }
                }
                Get-SubKey $results $SubKey $sub_key $Recurce # start to get  subkey name/value
            }else{
                Try{
                    $type=$sub_key.GetValueKind($Name)
                    $data=$sub_key.GetValue($Name)
                }Catch{ # not found specific name in this subkey
                    $result.Name+="|NotExist" ; $data="N/A" ; $type = "N/A"
                }Finally{}
                $result.Data=$data
                $result.Type=$type
                [void]$results.Add($result)
            }
        }else{ 
            Write-Log Error "Not found $SubKey in $Hive" $log_file
            $result.SubKey+="|NotExist"
            [void]$results.Add($result)
        }
        $sub_key.Close() ; $sub_key.Dispose() ; Remove-Variable sub_key
        $reg.Close() ; $reg.Dispose() ; Remove-Variable reg
		If( $ExportToCsv ){
		    $results|select VDI,Hive,SubKey,Name,@{n='Data';e={ $_.Data -join "," }},Type|Export-Csv -Path $OutputCSV -UseCulture -NoTypeInformation -Encoding UTF8 -Force -ea 0
			write-log Normal "The result has been saved in '$OutputCSV'" $log_file -print_to_console; return $null
		}
        return $results
    }Catch{
        Write-Log Error $_.Exception.Message $log_file -print_to_console
        return $null
    }
}
Function Set-RegistryValue(){
    [CmdletBinding()]param(
		[Parameter(ValueFromPipelineByPropertyName=$true)]
        [string]$vdi=$env:COMPUTERNAME,
        [ValidateSet("CurrentUser","LocalMachine","Users","ClassesRoot")]  # [Microsoft.Win32.RegistryHive].GetEnumNames()
		[Parameter(ValueFromPipelineByPropertyName=$true)]
		[string]$Hive='LocalMachine',
		[Parameter(ValueFromPipelineByPropertyName=$true)]
		[string]$SubKey,
		[Parameter(ValueFromPipelineByPropertyName=$true)]
		[string]$Name, # for ValueKind "DWord","QWord", both hexadecimal and decimalism can be used for value paramter
		[Parameter(ValueFromPipelineByPropertyName=$true)]
		$Data,         # such as when you see 0x00000200(512) in Registry Editor, both 0x00000200 (or 0x2000) and 512 can be used for value paramter
        [ValidateSet("Binary","DWord","QWord","String")] # [Microsoft.Win32.RegistryValueKind].GetEnumNames()
		[Parameter(ValueFromPipelineByPropertyName=$true)]
		[string]$Type="String",
        [string]$log_file="D:\temp\log.txt"
    )
    Process
    {
        Try{  
            If( $env:COMPUTERNAME -eq $vdi ){ # open local or remote /registry
                $reg = [Microsoft.Win32.RegistryKey]::OpenBaseKey($Hive,[Microsoft.Win32.RegistryView]::Default)
            }Else{
                $reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Hive, $vdi)
            }
			If( "Binary" -eq $Type -and ($Data -isnot [byte[]])){
			    $Data = [byte[]]$Data.split(",")
			}
            # C#--> CreateSubKey(String) --> Creates a new subkey or opens an existing subkey for write access.
            $reg.CreateSubKey($SubKey).SetValue($Name,$Data,[Microsoft.Win32.RegistryValueKind]::$Type)
            $reg.Close() ; $reg.Dispose() ; return $true
        }Catch{
            Write-Log Error $_.Exception.Message $log_file -print_to_console; return $false
        }
	}
}
Function Get-ADObject(){
	[cmdletBinding()]param(
        [Parameter(Mandatory=$true)]
        # [ValidatePattern("(?#e.g. nam\aaadmin)^(nam|apac|eur)\\\w{7,50}")] ICGSWDBU2N01_MSSQL\icgaastecc 
		[string]$ad_user,
		[Parameter(Mandatory=$False)]
        [ValidateSet("user","group")]
        [string]$ad_type='user',
        [ValidateScript({$_.Trim().Length -gt 0})]
		[string]$property="pwdlastset",  # memberof, userprincipalname ,manager ,lastlogontimestamp, pwdlastset ...
		[switch]$TransferTime,
		[switch]$all       # return all the ad object properties
	)
    $ad_domain,$ad_name=$ad_user.split('\')
    Try{
        If( [ADSI]::Exists("WinNT://$ad_domain/$ad_name,$ad_type") ){
            Try{
                $ad_searcher = New-Object DirectoryServices.DirectorySearcher([System.DirectoryServices.DirectoryEntry]"LDAP://dc=$ad_domain,dc=nsroot,dc=net","(samaccountname=$ad_name)") #SearchRoot and Filter
                $ad_object = $ad_searcher.FindOne()
                If($PSBoundParameters.ContainsKey('property')){ 
                    If( ($ad_object.Properties.Keys|out-string -ea 0) -inotmatch "\b$property\b" ){
                        write-Error "No such property '$property' exists for the AD $ad_type`: '$ad_user'" ; $ad_object = $null
                    }else{
                        If( ($ad_object=$ad_object.Properties.$property).Count -gt 1 ){
                            $ad_object= [array]$ad_object
                        }else{$ad_object= $ad_object[0]
                            If( $TransferTime ){
                               $ad_object=[datetime]::FromFileTime($ad_object)
                            }
                        }
                    }
                }elseif($all){ 
                    $ad_object_hashtable=New-Object System.Collections.Hashtable
                    $ad_object.Properties.GetEnumerator() |%{
                        If( ($value=$_.value).Count -gt 1 ){
                            $value=[array]$value
                        }else{$value=$value[0]}
                        [void]$ad_object_hashtable.Add($_.name,$value)
                    }
                    return $ad_object_hashtable
                }
            }Catch{
                write-Error $_ ; $ad_object = $null
            }Finally{}
        }
    }Catch{
        write-Error "No such AD object $ad_user exists,please double check spelling!"
    }
    return $ad_object
}
Function Get-MaxIOSizeTree(){  # Get-MaxIOSizeTree c:\temp\temp
    [CmdletBinding()]param(
        [string]$path=$pwd,
        $Script:result=@(),
        [long]$max_size=0,
        [int]$recurce_count=0,
        [switch]$TrimParentFolder
    )
    $max_size_path=$null ;$temp_path=$null ; [long]$temp_size=0
    ls $path|%{
        $temp_path=$_
        If('Directory' -eq $temp_path.Attributes ){
            $temp_size = (robocopy.exe $temp_path.fullname c:\fakepathduh /L /XJ /R:0 /W:1 /NP /E /BYTES /NFL /NDL /NJH /MT:64)[-4] -replace '\D+(\d+).*','$1'
        }Else{
            $temp_size= (gi $temp_path.fullname).length
        }
        If( $temp_size -ge $max_size){
            $max_size = $temp_size
            $max_size_path = $temp_path
        }
    }
    If( $TrimParentFolder ){
        $final_path= "." * $recurce_count +"\"+ $max_size_path.name
    }Else{
        $final_path=$max_size_path.fullname
    }
    $Script:result += [pscustomobject]@{
        Path=$final_path
        Size=$max_size
    }
    If('Directory' -eq $max_size_path.Attributes ){
        Get-MaxIOSizeTree $max_size_path.fullname $Script:result -recurce_count (++$recurce_count) -TrimParentFolder|out-null
    }
    return $Script:result
}
Function New-HttpRequestHeader(){ # mainly for JettyService
    [CmdletBinding()]param(
        [string]$content_type='application/json',
        [string]$session_id='mafwIj2zCN6cxenLU7DufBXYPQPVEUpsZ6RtbVfavS+/1fJtD3sbWtlHu7ItNRsG',
        [string]$authorization='Basic Ym90cnVubmVydXNlcjpDaXRpQDIwMTY=',
        [string]$log_file
    )
    Try{
        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("Content-Type", $content_type)
        $headers.Add("SESSIONID", $session_id)
        $headers.Add("Authorization", $authorization)
    }Catch{
        Write-Log Error $_.Exception.Message $log_file ; $headers = $null
    }
    return $headers
}
Function Invoke-JettyService(){
    [CmdletBinding()]param(
        $headers,
        [string]$vdi,
        [string[]]$command,
        [string]$log_file
    )
    $body_hash=[pscustomobject]@{
        commandName='test_command'
        hasChildren=$true
        commandList=@()
    }
    $command|%{
        $body_hash.commandList+=@{
            commandName='test_command'
            commandText=$_
        }
    }
    Try{
        $response = Invoke-RestMethod -Uri "http://$vdi`:8081/botrunner_task_executor/taskexec/cmd" -Method Post -Headers $headers -Body ($body_hash|ConvertTo-Json -Compress -Depth 5)
    }Catch{
        Write-Log Error $_.Exception.Message $log_file
    }
    return $response
}